package de.fmaul.dicheck.provider;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.apache.log4j.Logger;

import de.fmaul.dicheck.assertions.Assertion;
import de.fmaul.dicheck.assertions.AssertionFactory;
import de.fmaul.dicheck.model.Comment;
import de.fmaul.dicheck.model.Configurable;
import de.fmaul.dicheck.model.Property;
import de.fmaul.dicheck.model.TestCase;
import de.fmaul.dicheck.model.TestContainer;

public class LocalScriptTestProvider implements TestSuiteProvider, Configurable {
    private static Logger log = Logger.getLogger(LocalScriptTestProvider.class);

    private String suitePath = null;
    private final Properties baseProperties;
    
    private final AssertionFactory assertionFactory;

    private static class PropertyFileFilter implements FileFilter {
        public boolean accept(final File pathname) {
            return pathname.getName().toLowerCase().endsWith(".properties");
        };
    }

    private final PropertyFileFilter propertyFileFilter = new PropertyFileFilter();

    public LocalScriptTestProvider(final String testSuitePath,
            Properties baseProperties, AssertionFactory newAssertionFactory) {
        log.debug("Initializing LocalScriptTestProvider for path "
                + testSuitePath);
        this.suitePath = testSuitePath;
        this.baseProperties = baseProperties;
        assertionFactory = newAssertionFactory;
    }

    public Properties getProperties() {
        return baseProperties;
    }

    public TestContainer getTestSuite() {

        return readTestContainer(new File(suitePath), baseProperties);
    }

    private TestContainer readTestContainer(final File directory,
            final Properties parentProperties) {
        log.info("reading test container: " + directory.getPath());

        final Properties myProps = readPropertiesFromDir(directory,
                parentProperties);
        final String name = myProps.getProperty(Property.NAME, directory
                .getName());

        final TestContainer container = new TestContainer(name, myProps);

        final File[] allFiles = directory.listFiles();

        for (final File file : allFiles) {
            if (file.isDirectory()
                    && isValidContainerDirectory(container, file)) {
                container.getSubContainers().add(
                        readTestContainer(file, container.getProperties()));
            }
            if (file.isFile() && isValidScriptFile(container, file)) {
                container.getTestCases().add(
                        readTestCase(file, container.getProperties()));
            }
        }
        return container;
    }

    private boolean isValidScriptFile(final TestContainer container,
            final File file) {
        return file.getName().endsWith(".sql");
    }

    private boolean isValidContainerDirectory(final TestContainer container,
            final File directory) {
        boolean result = true;
        final String ignoreAll = container.getProperties().getProperty(
                Property.IGNORE_DIRS, ".svn;CVS;disabled");
        final String[] ignore = ignoreAll.split(";");
        for (final String ign : ignore) {
            if (directory.getName().toLowerCase().equals(ign.toLowerCase())) {
                result = false;
                break;
            }
        }

        return result;
    }

    private TestCase readTestCase(final File sqlFile,
            final Properties properties) {
        log.info("reading test definition: " + sqlFile.getPath());

        final TestCase test = new TestCase(sqlFile.getName(), properties);
        // TODO: Test specific properties!!

        final StringBuilder sqlScript = new StringBuilder();
        try {
            final BufferedReader reader = new BufferedReader(new FileReader(
                    sqlFile));

            String line = null;
            while ((line = reader.readLine()) != null) {
                sqlScript.append(line);
                sqlScript.append("\n");

                final Assertion a = assertionFactory.fromSqlString(line);
                if (a != null) {
                    test.getAssertions().add(a);
                }

                final Comment c = Comment.parseCommentFromSql(line);
                if (c != null) {
                    test.getComments().add(c);
                }
            }
            reader.close();
        } catch (final FileNotFoundException fnfe) {
            log.error("Sql script " + sqlFile.getName() + " not found.", fnfe);
        } catch (final IOException e) {
            log.error("Error reading sql script " + sqlFile.getName(), e);
        }

        test.setTestDefinition(sqlScript.toString());

        return test;
    }

    private Properties readPropertiesFromDir(final File directory,
            final Properties defaultProperties) {
        final Properties props = inheritProperties(defaultProperties);

        final File[] propFiles = directory.listFiles(propertyFileFilter);

        for (final File propertyFile : propFiles) {
            InputStream is = null;
            try {
                log.info("loading property file: " + propertyFile.getPath());
                is = new FileInputStream(propertyFile);
                props.load(is);
            } catch (final IOException e) {
                log.debug("Error loading property file "
                        + propertyFile.getPath());
            } finally {
                try {
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    log.error("Error closing InputStream.", e);
                }
            }

        }

        return props;
    }

    private Properties inheritProperties(final Properties defaultProperties) {

        final Properties props = new Properties();

        if (defaultProperties != null) {
            props.putAll(defaultProperties);

            // Remove Properties which not allow inheritance
            props.remove(Property.NAME);
        }
        return props;
    }

}
